﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Threading;
using System.Collections;
using System.IO;

namespace ListItemEditor.UI.FileDlg
{
    public class OpenFileFolderDialog : CommonDialog
    {
        #region nested types

        private class DlgWindow : NativeWindow
        {
            private OpenFileFolderDialog _dlg;

            public DlgWindow(OpenFileFolderDialog associatedDlg)
            {
                this._dlg = associatedDlg;
            }

            protected override void WndProc(ref Message m)
            {
                if (m.Msg == NativeMethods.WM_COMMAND)
                {
                    // the open button has child id 1
                    if (m.WParam.ToInt32() == 1)
                    {
                        this._dlg._isOpenClick = true;
                        // if we can get the selection ok - close the dialog directly
                        if (this._dlg.ApplyFilesFromSelection())
                        {
                            this._dlg.CloseDialog(1);
                            return;
                        }
                        // otherwise let the wnd porc continue with the default handling
                    }
                }
                base.WndProc(ref m);
            }
        }

        #endregion

        #region variables

        private int options;
        private DlgWindow _dlgWnd;
        private bool _isOpenClick;
        private string[] fileNames;
        private NativeMethods.CharBuffer charBuffer;
        private bool ignoreSecondFileOkNotification;
        private int okNotificationCount;

        #endregion

        #region properties

        public string[] FileNames
        {
            get
            {
                string[] fileNamesInternal = this.fileNames == null ? new string[]{} : (string[])this.fileNames.Clone();
                return fileNamesInternal;
            }
        }

        public string FileName
        {
            get
            {
                if (this.fileNames == null) return "";
                return this.fileNames[0];
            }
            set
            {
                if (value == null) { this.fileNames = null; }
                else { this.fileNames = new string[] { value }; }
            }
        }

        public bool Multiselect
        {
            get
            {
                return this.GetOption(0x200);
            }
            set
            {
                this.SetOption(0x200, value);
            }
        }

        public bool ReadOnlyChecked
        {
            get
            {
                return this.GetOption(1);
            }
            set
            {
                this.SetOption(1, value);
            }
        }

        public bool ShowReadOnly
        {
            get
            {
                return !this.GetOption(4);
            }
            set
            {
                this.SetOption(4, !value);
            }
        }

        public string Filter { get; set; }
        public int FilterIndex { get; set; }
        public string InitialDir { get; set; }
        public string Title { get; set; }
        public string DefaultExt { get; set; }
        public bool AddExtension { get; set; }

        protected virtual IntPtr Instance
        {
            get
            {
                return NativeMethods.GetModuleHandle(null);
            }
        }

        protected int Options
        {
            get
            {
                return (this.options & 0x100b1d);
            }
        }

        #endregion

        #region internal methods

        private bool ApplyFilesFromSelection()
        {
            List<string> selectedFiles = this.GetSelectedLVItems(this._dlgWnd.Handle);
            string fld = this.GetCurrentFolder(this._dlgWnd.Handle);

            if (selectedFiles.Count == 0 || string.IsNullOrEmpty(fld)) return false;

            this.fileNames = selectedFiles.Select(f => Path.Combine(fld, f)).ToArray();
            return true;
        }

        private bool DoFileOk(IntPtr lpOFN)
        {
            NativeMethods.OPENFILENAME_I openfilename_i = (NativeMethods.OPENFILENAME_I)NativeMethods.PtrToStructure(lpOFN, typeof(NativeMethods.OPENFILENAME_I));
            int options = this.options;
            int filterIndex = this.FilterIndex;
            string[] fileNames = this.fileNames;
            bool flag = true;
            try
            {
                this.options = (this.options & -2) | (openfilename_i.Flags & 1);
                this.FilterIndex = openfilename_i.nFilterIndex;
                this.charBuffer.PutCoTaskMem(openfilename_i.lpstrFile);
                Thread.MemoryBarrier();
                if ((this.options & 0x200) == 0)
                {
                    this.fileNames = new string[] { this.charBuffer.GetString() };
                }
                else
                {
                    this.fileNames = this.GetMultiselectFiles(this.charBuffer);
                }
            }
            finally
            {
                if (!flag)
                {
                    Thread.MemoryBarrier();
                    this.fileNames = fileNames;
                    this.options = options;
                    this.FilterIndex = filterIndex;
                }
            }
            return flag;
        }

        private string[] GetMultiselectFiles(NativeMethods.CharBuffer charBuffer)
        {
            string str = charBuffer.GetString();
            string str2 = charBuffer.GetString();
            if (str2.Length == 0)
            {
                return new string[] { str };
            }
            if (str[str.Length - 1] != '\\')
            {
                str = str + @"\";
            }
            ArrayList list = new ArrayList();
            do
            {
                if ((str2[0] != '\\') && (((str2.Length <= 3) || (str2[1] != ':')) || (str2[2] != '\\')))
                {
                    str2 = str + str2;
                }
                list.Add(str2);
                str2 = charBuffer.GetString();
            }
            while (str2.Length > 0);
            string[] array = new string[list.Count];
            list.CopyTo(array, 0);
            return array;
        }

        internal bool GetOption(int option)
        {
            return ((this.options & option) != 0);
        }

        internal void SetOption(int option, bool value)
        {
            if (value)
            {
                this.options |= option;
            }
            else
            {
                this.options &= ~option;
            }
        }

        protected override IntPtr HookProc(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam)
        {
            if (msg == 0x4e)
            {
                if (this._dlgWnd == null)
                {
                    this._dlgWnd = new DlgWindow(this);
                    this._dlgWnd.AssignHandle(NativeMethods.GetParent(new HandleRef(null, hWnd)));
                }

                try
                {
                    NativeMethods.OFNOTIFY structure = (NativeMethods.OFNOTIFY)NativeMethods.PtrToStructure(lparam, typeof(NativeMethods.OFNOTIFY));
                    switch (structure.hdr_code)
                    {
                        case -603:
                            if (this._isOpenClick)
                            {
                                this._isOpenClick = false;
                            }
                            return IntPtr.Zero;
                        case -606:
                            if (this.ignoreSecondFileOkNotification)
                            {
                                if (this.okNotificationCount != 0)
                                {
                                    this.ignoreSecondFileOkNotification = false;
                                    NativeMethods.SetWindowLong(new HandleRef(null, hWnd), 0, new HandleRef(null, NativeMethods.InvalidIntPtr));
                                    return NativeMethods.InvalidIntPtr;
                                }
                                this.okNotificationCount = 1;
                            }

                            if (!this.DoFileOk(structure.lpOFN))
                            {
                                NativeMethods.SetWindowLong(new HandleRef(null, hWnd), 0, new HandleRef(null, NativeMethods.InvalidIntPtr));
                                return NativeMethods.InvalidIntPtr;
                            }

                            this._dlgWnd.ReleaseHandle();
                            return IntPtr.Zero;
                            
                        case -604:
                            this.ignoreSecondFileOkNotification = true;
                            this.okNotificationCount = 0;
                            return IntPtr.Zero;

                        case -602:
                            {
                                NativeMethods.OPENFILENAME_I openfilename_i = (NativeMethods.OPENFILENAME_I)NativeMethods.PtrToStructure(structure.lpOFN, typeof(NativeMethods.OPENFILENAME_I));
                                int num = (int)NativeMethods.SendMessage(new HandleRef(this, this._dlgWnd.Handle), 0x464, IntPtr.Zero, IntPtr.Zero);
                                if (num > openfilename_i.nMaxFile)
                                {
                                    try
                                    {
                                        int size = num + 0x800;
                                        NativeMethods.CharBuffer buffer = new NativeMethods.CharBuffer(size);
                                        IntPtr ptr = buffer.AllocCoTaskMem();
                                        Marshal.FreeCoTaskMem(openfilename_i.lpstrFile);
                                        openfilename_i.lpstrFile = ptr;
                                        openfilename_i.nMaxFile = size;
                                        this.charBuffer = buffer;
                                        Marshal.StructureToPtr(openfilename_i, structure.lpOFN, true);
                                        Marshal.StructureToPtr(structure, lparam, true);
                                    }
                                    catch
                                    {
                                    }
                                }
                                this.ignoreSecondFileOkNotification = false;
                                return IntPtr.Zero;
                            }
                        case -601:
                            //CommonDialog.MoveToScreenCenter(this.dialogHWnd);
                            return IntPtr.Zero;

                        default:
                            return IntPtr.Zero;
                    }
                }
                catch
                {
                    CloseDialog(0);
                    throw;
                }
            }
            return IntPtr.Zero;
        }

        private void CloseDialog(int result)
        {
            if (this._dlgWnd != null)
            {
                IntPtr handle = this._dlgWnd.Handle;
                this._dlgWnd.ReleaseHandle();
                NativeMethods.EndDialog(new HandleRef(this, handle), new IntPtr(result));
            }
        }

        private string GetCurrentFolder(IntPtr dlgHWnd)
        {
            IntPtr buffer = Marshal.AllocHGlobal(1024);
            try
            {
                IntPtr res = NativeMethods.SendMessage(new HandleRef(null, dlgHWnd), (int)NativeMethods.CDM_GETFOLDERPATH, new IntPtr(512), buffer);
                if (res.ToInt32() <= 1024)
                {
                    string fld = Marshal.PtrToStringAuto(buffer);
                    return fld;
                }
                else return null;
            }
            finally
            {
                if (buffer != IntPtr.Zero) Marshal.FreeHGlobal(buffer);
            }
        }

        private List<string> GetSelectedLVItems(IntPtr dlgHwnd)
        {
            List<string> items = new List<string>();

            IntPtr vHandle = NativeMethods.FindWindowEx(dlgHwnd, IntPtr.Zero, "SHELLDLL_DefView", null);
            if (vHandle == IntPtr.Zero) return items;
            vHandle = NativeMethods.FindWindowEx(vHandle, IntPtr.Zero, "SysListView32", null);
            if (vHandle == IntPtr.Zero) return items;


            NativeMethods.LVITEM lvi = new NativeMethods.LVITEM();
            lvi.mask = (int)(NativeMethods.LVIF_TEXT | NativeMethods.LVIF_STATE);
            lvi.cchTextMax = 512;
            lvi.stateMask = -1;
            lvi.iItem = 0;            // the zero-based index of the ListView item
            lvi.iSubItem = 0;         // the one-based index of the subitem, or 0 if this structure refers to an item rather than a subitem
            lvi.pszText = Marshal.AllocHGlobal(1024);

            string itemText = "";
            IntPtr ptrLvi = IntPtr.Zero;
            try
            {
                ptrLvi = Marshal.AllocHGlobal(Marshal.SizeOf(lvi));
                for (int i = 0; ; i++)
                {
                    lvi.iItem = i;
                    Marshal.StructureToPtr(lvi, ptrLvi, false);

                    IntPtr res = NativeMethods.SendMessage(new HandleRef(null, vHandle), (int)NativeMethods.ListViewMessages.LVM_GETITEMW, IntPtr.Zero, ptrLvi);
                    if (res == IntPtr.Zero) break;

                    NativeMethods.LVITEM retLvi = (NativeMethods.LVITEM)Marshal.PtrToStructure(ptrLvi, typeof(NativeMethods.LVITEM));
                    if ((retLvi.state & 2) == 2)
                    {
                        itemText = Marshal.PtrToStringAuto(lvi.pszText);
                        items.Add(itemText);
                    }
                }
            }
            finally
            {
                if (ptrLvi != null) Marshal.FreeHGlobal(ptrLvi);
                if (lvi.pszText != IntPtr.Zero) Marshal.FreeHGlobal(lvi.pszText);
            }

            return items;
        }

        #endregion

        #region overrides

        public override void Reset()
        {
        }

        protected override bool RunDialog(IntPtr hwndOwner)
        {
            bool flag;
            _dlgWnd = null;

            NativeMethods.OPENFILENAME_I ofn = new NativeMethods.OPENFILENAME_I();
            try
            {
                this.charBuffer = new NativeMethods.CharBuffer(0x2000);
                if (this.fileNames != null)
                {
                    this.charBuffer.PutString(this.fileNames[0]);
                }
                ofn.lStructSize = Marshal.SizeOf(typeof(NativeMethods.OPENFILENAME_I));
                if ((Environment.OSVersion.Platform != PlatformID.Win32NT) || (Environment.OSVersion.Version.Major < 5))
                {
                    ofn.lStructSize = 0x4c;
                }
                ofn.hwndOwner = hwndOwner;
                ofn.hInstance = this.Instance;
                ofn.lpstrFilter = this.Filter ?? string.Empty;
                ofn.nFilterIndex = this.FilterIndex;
                ofn.lpstrFile = this.charBuffer.AllocCoTaskMem();
                ofn.nMaxFile = 2000;
                ofn.lpstrInitialDir = this.InitialDir ?? string.Empty;
                ofn.lpstrTitle = this.Title ?? string.Empty;
                ofn.Flags = this.Options | 0x880020;
                ofn.lpfnHook = this.HookProc;
                ofn.FlagsEx = 0x1000000;
                if ((this.DefaultExt != null) && this.AddExtension)
                {
                    ofn.lpstrDefExt = this.DefaultExt;
                }
                flag = NativeMethods.GetOpenFileName(ofn);
            }
            finally
            {
                _dlgWnd = null;
                this.charBuffer = null;
                if (ofn.lpstrFile != IntPtr.Zero)
                {
                    Marshal.FreeCoTaskMem(ofn.lpstrFile);
                }
            }
            return flag;

        }

        #endregion
    }
}